/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: posix_mutex.c
 *
 * Purpose: implementation mutex with posix API
 *
 * Developer:
 *   wen.gu , 2022-01-21
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "config.h"
#if defined(HAS_POSIX_MUTEX_API) && (HAS_POSIX_MUTEX_API) 
#include "cbus/core/cbus_mutex.h"

#include <pthread.h>
#include <time.h>
#include <sys/time.h>
//#include <pthread.h>
#include <string.h>
#include <unistd.h>

#define THD_PRINT printf

/******************************************************************************
 **    MACROS
 ******************************************************************************/
#define USEC_PER_SEC (1000 * 1000)
#define USEC_PER_MSEC (1000)
#define MSEC_PER_SEC (1000)
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/

/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/
/**
 * @brief initialize a mutex instance
 * 
 * @param mtx [io] mutex instance
 * @return void
 */
cbus_mutex_t cbus_mutex_create(void)
{
    pthread_mutexattr_t attr;
    pthread_mutex_t* mutex = (pthread_mutex_t*)malloc(sizeof(pthread_mutex_t));

    if (!mutex)
    {
        THD_PRINT("alloc mutex instance failed\n");
        abort();
    }
    
    if (0 != pthread_mutexattr_init (&attr))
    {
        THD_PRINT("pthread_mutexattr_init failed\n");
        free(mutex);
        abort();
    }
#if defined(_GNU_SOURCE) || (XOPEN_SOURCE >= 500)
#ifdef NDEBUG
    pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_FAST_NP);
#else
    pthread_mutexattr_settype (&attr, PTHREAD_MUTEX_ERRORCHECK_NP);
#endif
#endif

    if (0 != pthread_mutex_init (mutex, &attr))
    {
        THD_PRINT("pthread_mutex_init failed\n");
        free(mutex);
        abort();
    }
    pthread_mutexattr_destroy(&attr);

    return (cbus_mutex_t)mutex; 
}

/**
 * @brief destroy a mutex instance
 * 
 * @param mtx [in] mutex instance
 * @return void
 */
void cbus_mutex_destroy(cbus_mutex_t mtx)
{
    pthread_mutex_destroy((pthread_mutex_t*)mtx);
    free(mtx);
}

/**
 * @brief do lock with mutex instance
 * 
 * @param mtx [in] mutex intance
 * @return void
 */
void cbus_mutex_lock(cbus_mutex_t mtx)
{
    pthread_mutex_lock((pthread_mutex_t*)mtx);
}

/**
 * @brief do unlock with mutex instance
 * 
 * @param mtx [in] mutex instance
 * @return void
 */
void cbus_mutex_unlock(cbus_mutex_t mtx)
{
    pthread_mutex_unlock((pthread_mutex_t*)mtx);
}

/**
 * @brief initialize a condition variable instance
 * 
 * @param void
 * @return void 
 */
cbus_condition_variable_t cbus_cv_create(void)
{
    pthread_cond_t* cond = (pthread_cond_t*)malloc(sizeof(pthread_cond_t));

    if (!cond)
    {
        THD_PRINT("alloc pthread_cond_t failed\n");
    }
    else
    {
        GS32 ret = pthread_cond_init(cond, NULL) ;
        if (0 != ret)
        {
            THD_PRINT("pthread_cond_init failed,ret(%d)\n", ret);
            free(cond);
            cond = NULL;
        }
    }

    return (cbus_condition_variable_t)cond;
}

/**
 * @brief destroy a condition variable instance
 * 
 * @param cv [in] condition variable instance
 * @return void
 */
void cbus_cv_destroy(cbus_condition_variable_t cv)
{
    pthread_cond_destroy((pthread_cond_t*)cv);
    free(cv);
}

/**
 * @brief wait forever with condition variable instance
 * 
 * @param cv [in] condition variable instance
 * @param mtx [in] mutex instance
 * @return void
 */
void cbus_cv_wait(cbus_condition_variable_t cv, cbus_mutex_t mtx)
{
    pthread_cond_wait((pthread_cond_t*)cv, (pthread_mutex_t*)mtx);
}

/**
 * @brief timed wait for condition variable instance
 * 
 * @param cv [in] condition variable instance
 * @param mtx [in] mutex instance
 * @param wait_ms [in] how long to wait,
 * @return GErrc if timeout: G_ErrTimeout, if success:G_OK 
 */
GErrc cbus_cv_wait_for(cbus_condition_variable_t cv, cbus_mutex_t mtx, GU32 wait_ms)
{
    struct timespec ts;
    struct timeval tv;
    gettimeofday(&tv,NULL);

	GS64 curTimeUs = (GS64)tv.tv_sec * USEC_PER_SEC + tv.tv_usec;

	curTimeUs += (wait_ms * USEC_PER_MSEC);
	ts.tv_sec  = curTimeUs / USEC_PER_SEC;
	ts.tv_nsec = curTimeUs % USEC_PER_SEC;

    GS32 ret = pthread_cond_timedwait((pthread_cond_t*)cv, (pthread_mutex_t*)mtx, &ts);

    if (0 != ret)
    {
        //THD_PRINT("cond_timedwait failed,ret(%d)\n", ret);
        return G_ErrTimeout;
    }

    return G_OK;
}

/**
 * @brief to wake up one condition wait
 * 
 * @param cv [in] condition variable instance
 * @return void 
 */
void cbus_cv_notify_one(cbus_condition_variable_t cv)
{
    pthread_cond_signal((pthread_cond_t*)cv);
}

/**
 * @brief to wake up all condition wait
 * 
 * @param cv [in] condition variable instance
 * @return void
 */
void cbus_cv_notify_all(cbus_condition_variable_t cv)
{
    pthread_cond_broadcast((pthread_cond_t*)cv);
}


#endif /** defined HAS_POSIX_MUTEX_API */
